﻿using System;
using System.Collections.Generic;
using System.Linq;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Model
{
    /// <summary>
    /// GIS地理轨迹生成器
    /// </summary>
    public class GISTrailGenerator
    {
        private readonly IEnumerable<GISTrailSegment> _segments;
        private readonly int _count;

        private GISTrailSegment _lastSegment;
        private int _index = 0;

        /// <summary>
        /// 构造函数初始化
        /// </summary>
        /// <param name="segments">坐标段</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public GISTrailGenerator(IEnumerable<GISTrailSegment> segments)
        {
            if (segments == null)
            {
                throw new ArgumentNullException(nameof(segments));
            }

            this._count = segments.Count();
            if (this._count < 1)
            {
                throw new ArgumentException("至少需要一段才能生成轨迹.");
            }

            this._segments = segments;
            this._lastSegment = segments.ElementAt(0);
        }



        /// <summary>
        /// 尝试获取下一个坐标点,获取成功返回true,获取失败返回false
        /// </summary>
        /// <param name="point">获取到的坐标点</param>
        /// <returns>获取成功返回true,获取失败返回false</returns>
        public bool TryNext(out GISPoint3 point)
        {
            if (this._index >= this._count)
            {
                point = null;
                return false;
            }

            if (this._lastSegment.TryNext(out point))
            {
                return true;
            }


            while (this._index < this._count)
            {
                this._index++;
                if (this._index >= this._count)
                {
                    //最后一项
                    point = this._lastSegment.EndPoint;
                    return true;
                }

                this._lastSegment = this._segments.ElementAt(this._index);
                if (this._lastSegment.TryNext(out point))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 重置
        /// </summary>
        public void Reset()
        {
            for (int i = 0; i < this._count; i++)
            {
                this._segments.ElementAt(i).Reset();
            }
            this._index = 0;
            this._lastSegment = this._segments.ElementAt(0);
        }


        /// <summary>
        /// 创建GIS地理坐标轨迹生成器
        /// </summary>
        /// <param name="point3s">坐标集合</param>
        /// <param name="lonStep">经度步进,单位:°</param>
        /// <param name="latStep">纬度步进,单位:°</param>
        /// <param name="altStep">高度步进,单位:米</param>
        /// <returns>GIS地理坐标轨迹生成器</returns>
        /// <exception cref="ArgumentNullException">坐标集合为null</exception>
        /// <exception cref="ArgumentException">坐标集合中的坐标数太少</exception>
        public static GISTrailGenerator CreateTrailGenerator(IEnumerable<GISPoint3> point3s, double lonStep, double latStep, double altStep)
        {
            if (point3s == null)
            {
                throw new ArgumentNullException(nameof(point3s));
            }

            int pointCount = point3s.Count();
            if (pointCount < 2)
            {
                throw new ArgumentException(nameof(point3s), "至少需要两个点才能形成轨迹.");
            }

            var segments = new List<GISTrailSegment>(pointCount);
            GISPoint3 lastPoint = null;
            int lonCount, latCount, altCount;

            foreach (var point3 in point3s)
            {
                if (lastPoint == null)
                {
                    lastPoint = point3;
                    continue;
                }

                if (double.IsNaN(lonStep) || lonStep < 0.0000001d)
                {
                    lonCount = 0;
                }
                else
                {
                    lonCount = (int)Math.Abs((point3.Lon - lastPoint.Lon) / lonStep);
                }

                if (double.IsNaN(latStep) || latStep < 0.0000001d)
                {
                    latCount = 0;
                }
                else
                {
                    latCount = (int)Math.Abs((point3.Lat - lastPoint.Lat) / latStep);
                }

                if (double.IsNaN(altStep) || altStep < 0.01d)
                {
                    altCount = 0;
                }
                else
                {
                    altCount = (int)Math.Abs((point3.Alt - lastPoint.Alt) / altStep);
                }

                int count = Math.Max(Math.Max(lonCount, latCount), altCount);
                segments.Add(new GISTrailSegment(lastPoint, point3, count));
                lastPoint = point3;
            }

            return new GISTrailGenerator(segments);
        }

        /// <summary>
        /// 创建GIS地理坐标轨迹生成器
        /// </summary>
        /// <returns>GIS地理坐标轨迹生成器</returns>
        /// <exception cref="ArgumentNullException">坐标集合为null</exception>
        /// <exception cref="ArgumentException">坐标集合中的坐标数太少</exception>
        public static GISTrailGenerator Parse(string trailStr)
        {
            GISTrailGenerator trailGenerator;
            string err;
            if (PrimitiveParse(trailStr, out trailGenerator, out err))
            {
                return trailGenerator;
            }
            else
            {
                throw new InvalidCastException(trailStr);
            }
        }

        /// <summary>
        /// 创建GIS地理坐标轨迹生成器
        /// </summary>
        /// <param name="trailStr">轨迹描述字符串,格式:(86.88,28.91)-(87.589,29.652)-100|(87.589,29.652,1000)-(88.758,30.242,3000)*100</param>
        /// <param name="trailGenerator">输出的轨迹生成器对象</param>
        /// <returns>GIS地理坐标轨迹生成器</returns>
        /// <exception cref="ArgumentNullException">坐标集合为null</exception>
        /// <exception cref="ArgumentException">坐标集合中的坐标数太少</exception>
        public static bool TryParse(string trailStr, out GISTrailGenerator trailGenerator)
        {
            try
            {
                string err;
                return PrimitiveParse(trailStr, out trailGenerator, out err);
            }
            catch (Exception ex)
            {
                ZLoger.Warn(ex);
                trailGenerator = null;
                return false;
            }
        }

        private static bool PrimitiveParse(string trailStr, out GISTrailGenerator trailGenerator, out string err)
        {
            trailGenerator = null;
            err = null;
            List<GISTrailSegment> segments = new List<GISTrailSegment>();
            //(86.88,28.91)-(87.589,29.652)-100|(87.589,29.652)-(88.758,30.242)-100
            string[] segStrArr = trailStr.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            int insertCount;
            GISPoint3 p1, p2;
            char[] separator1 = new char[] { '-', '*' };
            char[] separator2 = new char[] { ',', '，' };

            foreach (var segStr in segStrArr)
            {
                //segStr:(86.88,28.91)-(87.589,29.652)-100
                string[] strs = segStr.Split(separator1, StringSplitOptions.RemoveEmptyEntries);

                //str:(86.88,28.91)
                if (!TryParseGISPoint3(strs[0], separator2, out p1, out err))
                {
                    return false;
                }

                if (!TryParseGISPoint3(strs[1], separator2, out p2, out err))
                {
                    return false;
                }

                if (!int.TryParse(strs[2].Trim('(', ')', '（', '）'), out insertCount))
                {
                    err = $"航迹轨迹线插值数\"{strs[2]}\"无效";
                    return false;
                }

                segments.Add(new GISTrailSegment(p1, p2, insertCount));
            }

            trailGenerator = new GISTrailGenerator(segments);
            return true;
        }

        private static bool TryParseGISPoint3(string str, char[] separator2, out GISPoint3 point, out string err)
        {
            double lon, lat, alt;
            point = null;

            string[] lonLatArr = str.Trim('(', ')', '（', '）').Split(separator2, StringSplitOptions.RemoveEmptyEntries);
            if (lonLatArr.Length == 2)
            {
                if (!double.TryParse(lonLatArr[0].Trim(), out lon))
                {
                    err = $"航迹轨迹线坐标经度值\"{lonLatArr[0]}\"无效";
                    return false;
                }

                if (!double.TryParse(lonLatArr[1].Trim(), out lat))
                {
                    err = $"航迹轨迹线坐标纬度值\"{lonLatArr[1]}\"无效";
                    return false;
                }

                alt = 0d;
            }
            else if (lonLatArr.Length == 3)
            {
                if (!double.TryParse(lonLatArr[0].Trim(), out lon))
                {
                    err = $"航迹轨迹线坐标经度值\"{lonLatArr[0]}\"无效";
                    return false;
                }

                if (!double.TryParse(lonLatArr[1].Trim(), out lat))
                {
                    err = $"航迹轨迹线坐标纬度值\"{lonLatArr[1]}\"无效";
                    return false;
                }

                if (!double.TryParse(lonLatArr[2].Trim(), out alt))
                {
                    err = $"航迹轨迹线坐标高值\"{lonLatArr[2]}\"无效";
                    return false;
                }
            }
            else
            {
                err = $"航迹轨迹线坐标经度值\"{str}\"无效";
                return false;
            }

            point = new GISPoint3(lon, lat, alt);
            err = null;
            return true;
        }


    }

    /// <summary>
    /// GIS轨迹段
    /// </summary>
    public class GISTrailSegment
    {
        /// <summary>
        /// 开始坐标
        /// </summary>
        public readonly GISPoint3 BeginPoint;

        /// <summary>
        /// 结束坐标
        /// </summary>
        public readonly GISPoint3 EndPoint;

        /// <summary>
        /// 插值数
        /// </summary>
        public readonly int InsertValueCount;

        /// <summary>
        /// 获取指示已获取完成标识[ture:已获取完成;false:未获取完成]
        /// </summary>
        public bool IsEnd
        {
            get
            {
                return this._count >= this.InsertValueCount;
            }
        }


        private readonly double _lonStep;
        private readonly double _latStep;
        private readonly double _altStep;

        private double _lon;
        private double _lat;
        private double _alt;
        private int _count = 0;

        /// <summary>
        /// 构造函数初始化
        /// </summary>
        /// <param name="beginPoint">开始坐标</param>
        /// <param name="endPoint">结束坐标</param>
        /// <param name="insertValueCount">插值数</param>
        public GISTrailSegment(GISPoint3 beginPoint, GISPoint3 endPoint, int insertValueCount)
        {
            if (beginPoint == null)
            {
                throw new ArgumentNullException(nameof(beginPoint));
            }

            if (endPoint == null)
            {
                throw new ArgumentNullException(nameof(endPoint));
            }

            if (insertValueCount < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(insertValueCount), "插值数不能小于0");
            }

            this.BeginPoint = beginPoint;
            this.EndPoint = endPoint;
            this.InsertValueCount = insertValueCount;

            this._lonStep = (endPoint.Lon - beginPoint.Lon) / insertValueCount;
            this._latStep = (endPoint.Lat - beginPoint.Lat) / insertValueCount;
            this._altStep = (endPoint.Alt - beginPoint.Alt) / insertValueCount;

            this._lon = beginPoint.Lon;
            this._lat = beginPoint.Lat;
            this._alt = beginPoint.Alt;
        }

        /// <summary>
        /// 重置
        /// </summary>
        public void Reset()
        {
            this._lon = this.BeginPoint.Lon;
            this._lat = this.BeginPoint.Lat;
            this._alt = this.BeginPoint.Alt;
            this._count = 0;
        }

        /// <summary>
        /// 尝试获取下一个坐标点,获取成功返回true,获取失败返回false
        /// </summary>
        /// <param name="point">获取到的坐标点</param>
        /// <returns>获取成功返回true,获取失败返回false</returns>
        public bool TryNext(out GISPoint3 point)
        {
            if (this._count < this.InsertValueCount)
            {
                point = new GISPoint3(Math.Round(this._lon, 6), Math.Round(this._lat, 6), Math.Round(this._alt, 3));

                this._lon += this._lonStep;
                this._lat += this._latStep;
                this._alt += this._altStep;
                this._count++;

                return true;
            }
            else
            {
                point = null;
                return false;
            }
        }

    }

}
